﻿using System;
using System.Collections.Generic;
using System.Web;
using System.Xml;
using System.Configuration;

using JigsawService.Utility;
using JigsawService.Controllers;
using JigsawService.DataAccess.Security;

namespace JigsawService.Parsers
{
    public class XMLParser : JigsawBase
    {
        APIMessage _apimessage;

        public XMLParser(APIMessage apiMessage)
        {
            _apimessage = apiMessage;
        }

        /// <summary>
        /// Parses the API request message and hands it off for processing to the appropriate controller;
        /// Serializes the results from the controller into an API response message, and returns it to the service
        /// </summary>
        /// <param name="requestMessage">XML API request message</param>
        public void ProcessAPIRequest(string requestMessage)
        {
            _apimessage.StateMessage = "XMLParser ProcessAPIRequest: ";
            
            XmlDocument xmlRequest = new XmlDocument();
            if (requestMessage != null && requestMessage != "")
            {
                // add xml header to request fragment, then load the encoded XML message into an XML document object
                string xmlReqStr = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>" + requestMessage;
                xmlRequest.LoadXml(xmlReqStr);

                // begin to parse the request message XML into APIMessage property values
                ParseXMLRequest(xmlRequest);

                // if XML parsing and property values assignment was successful
                if (_apimessage.ProcessState == StatusCodes.Parsed.ToString())
                {
                    // check method TimeToLive (milliseconds)
                    string TTL = ConfigurationManager.AppSettings["TimeToLive"].ToString();
                    string svrmillisec = GetTimeInMilliseconds();

                    double currenttime = Convert.ToDouble(svrmillisec);
                    double requesttime = Convert.ToDouble(_apimessage.RequestTime);
                    double elapsedtime = currenttime - requesttime;

                    // if difference in time is greater than TTL value in milliseconds, the method TimeToLive has expired
                    if (elapsedtime <= Convert.ToDouble(TTL))
                    {
                        // authenticate user and store authorization data in methodinput property
                        EncryptionUtility encutil = new EncryptionUtility();
                        string tmppassword = encutil.PasswordHash(_apimessage.UserName, _apimessage.Password);

                        UserAccountDA secda = new UserAccountDA(_apimessage);
                        secda.ValidateUser();

                        if (_apimessage.ProcessState == StatusCodes.Authorized.ToString())
                        {
                            // call appropriate controller
                            switch (_apimessage.APIName)
                            {
                                case "Events":
                                    Events events = new Events(_apimessage);
                                    events.Controller();
                                    break;

                                case "Errors":

                                    break;

                                case "FileTransfer":

                                    break;

                                case "MediaCollector":

                                    break;

                                case "Arcanum":

                                    break;

                                case "Testing":
                                    Testing testing = new Testing(_apimessage);
                                    testing.Controller();
                                    break;

                                case "SystemAdmin":
                                    SystemAdmin sysadmin = new SystemAdmin(_apimessage);
                                    sysadmin.Controller();
                                    break;

                                default:
                                    throw new Exception("Unknown API name.");
                            }
                        }
                        else
                        {
                            throw new Exception("Error validating user account.");
                        }
                    }
                    else
                    {
                        throw new Exception("The request message time-to-live expired.");
                    }
                }
                else
                {
                    throw new Exception("Error parsing the request message.");
                }
            }
        }

        // ****************************************
        // ****************************************

        // Input XML Format
        // ----------------
        //<RequestMessage>
        //    <SessionGUID />   -- unique identifier per user session
        //    <RequestGUID />   -- unique identifier per request message
        //    <RequestTime />   -- milliseconds since Jan 1 1970
        //    <UserName />      -- cleartext username
        //    <Password />      -- cleartext password (requires message to be encrypted during transport)
        //    <MethodName />    -- apiname.methodname.versionname (fully qualified method name)
        //    <VersionParams>   -- collection of parameter name+value pairs enclosed in CDATA block
        //        <![CDATA[ param1name^param1value|param2name^param2value ]]>
        //    </VersionParams>
        //</RequestMessage>
        // -----------------
        // * VersionParams cannot contain ^, |, or characters that conflict with CDATA block

        // ****************************************
        // ****************************************

        private void ParseXMLRequest(XmlDocument xdoc)
        {
            _apimessage.StateMessage = "XMLParser ParseXMLRequest: ";
            _apimessage.ProcessState = StatusCodes.Parsed.ToString();
            _apimessage.StateMessage = "Parse OK";
            string _delim = "";

            // test for sessionguid
            if (xdoc.SelectSingleNode("RequestMessage/SessionGUID") != null)
            {
                _apimessage.SessionGUID = xdoc.SelectSingleNode("RequestMessage/SessionGUID").InnerText;
            }
            else
            {
                _apimessage.ProcessState = StatusCodes.Error.ToString();
                _apimessage.StateMessage = _delim + "Missing SessionGUID";
                _delim = ", ";
            }

            // test for requestguid
            if (xdoc.SelectSingleNode("RequestMessage/RequestGUID") != null)
            {
                _apimessage.RequestGUID = xdoc.SelectSingleNode("RequestMessage/RequestGUID").InnerText;
            }
            else
            {
                _apimessage.ProcessState = StatusCodes.Error.ToString();
                _apimessage.StateMessage = _delim + "Missing RequestGUID";
                _delim = ", ";
            }

            // test for requesttime
            if (xdoc.SelectSingleNode("RequestMessage/RequestTime") != null)
            {
                _apimessage.RequestTime = xdoc.SelectSingleNode("RequestMessage/RequestTime").InnerText;
            }
            else
            {
                _apimessage.ProcessState = StatusCodes.Error.ToString();
                _apimessage.StateMessage = _delim + "Missing RequestTime";
                _delim = ", ";
            }

            // test for username
            if (xdoc.SelectSingleNode("RequestMessage/UserName") != null)
            {
                _apimessage.UserName = xdoc.SelectSingleNode("RequestMessage/UserName").InnerText;
            }
            else
            {
                _apimessage.ProcessState = StatusCodes.Error.ToString();
                _apimessage.StateMessage += _delim + "Missing UserName";
                _delim = ", ";
            }

            // test for password
            if (xdoc.SelectSingleNode("RequestMessage/Password") != null)
            {
                _apimessage.Password = xdoc.SelectSingleNode("RequestMessage/Password").InnerText;
            }
            else
            {
                _apimessage.ProcessState = StatusCodes.Error.ToString();
                _apimessage.StateMessage += _delim + "Missing Password";
                _delim = ", ";
            }

            // test for method string
            if (xdoc.SelectSingleNode("RequestMessage/MethodName") != null)
            {
                string methname = xdoc.SelectSingleNode("RequestMessage/MethodName").InnerText;

                // parse namespace from method string
                string[] apiParams = methname.Split(new char[] { '.' });

                if (apiParams.Length == 3)
                {
                    _apimessage.APIName = apiParams[0].ToString();
                    _apimessage.APIString = methname;
                }
                else
                {
                    _apimessage.ProcessState = StatusCodes.Error.ToString();
                    _apimessage.StateMessage += _delim + "Unable to parse the APIName.MethodName.VersionName";
                    _delim = ", ";
                }
            }
            else
            {
                _apimessage.ProcessState = StatusCodes.Error.ToString();
                _apimessage.StateMessage += _delim + "Missing MethodName";
                _delim = ", ";
            }

            // internal method params are optional
            if (xdoc.SelectSingleNode("RequestMessage/VersionParams") != null)
            {
                XmlNode resultNode = xdoc.SelectSingleNode("RequestMessage/VersionParams");
                if (resultNode.InnerXml.Length > 0)
                {
                    // if any internal params are found, strip off CDATA block and set property value
                    string paramstr = resultNode.InnerText;
                    paramstr = paramstr.Replace("<![CDATA[", "");
                    paramstr = paramstr.Replace("]]>", "");

                    _apimessage.VersionParamString = paramstr;
                }
            }
        }

        // ****************************************
        // ****************************************

        // Output XML Format
        // -----------------
        //<ResponseMessage>
        //    <RequestGUID />   -- unique identifier per request message
        //    <RequestTime />   -- milliseconds since Jan 1 1970
        //    <UserName />      -- cleartext username
        //    <MethodName />    -- apiname.methodname.versionname (fully qualified method name)
        //    <StartTime />     -- milliseconds time when API processing started
        //    <EndTime />       -- milliseconds time when API processing ended
        //    <ResponseCode />  -- Success, Empty, Error, Exception
        //    <ResponseType />  -- Int, Number, Text, XML, JSON, DataTable
        //    <MethodResult>    -- internal method results enclosed in CDATA block
        //        <![CDATA[ internal method results ]]>
        //    </MethodResult>
        //</ResponseMessage>
        // -----------------
        // * Result cannot contain characters that conflict with CDATA block

        // ****************************************
        // ****************************************

        public string SerializeXMLResponse()
        {
            _apimessage.StateMessage = "XMLParser SerializeXMLResponse: ";

            string responseXML = "<ResponseMessage>" +
                                  "<RequestGUID>" + _apimessage.RequestGUID + "</RequestGUID>" +
                                  "<RequestTime>" + _apimessage.RequestTime + "</RequestTime>" +
                                  "<UserName>" + _apimessage.UserName + "</UserName>" +
                                  "<MethodName>" + _apimessage.APIString + "</MethodName>" +
                                  "<StartTime>" + _apimessage.StartTime + "</StartTime>" +
                                  "<EndTime>" + _apimessage.EndTime + "</EndTime>" +
                                  "<ResponseCode>" + _apimessage.ResponseCode + "</ResponseCode>" +
                                  "<ResponseType>" + _apimessage.ResponseType + "</ResponseType>" +
                                  "<MethodResult><![CDATA[" + _apimessage.MethodResult + "]]></MethodResult></ResponseMessage>";

            return responseXML;
        }

    }
}